﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TribesPSO
{
    /// <summary>
    /// The base class for all particles
    /// </summary>
    public abstract class Particle
    {
        /// <summary>
        /// This is the number of past positions that are "remembered" by the particle.  This value should be
        /// greater or equal to 2.  Values above 2 are only useful for diagnostic purposes because they aren't used
        /// to determine "goodness".
        /// </summary>
        private const int HistoryLength = 2;

        //Maintains a history of whether or not the particle has improved its best position
        private readonly List<bool> solutionHistory;

        /// <summary>
        /// Gets the best solution that the particle has ever seen
        /// </summary>
        public Solution BestSolution
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the current position of the particle
        /// </summary>
        public EuclidianVector Position
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the solution to the particle's IObjectiveFunction at its current location
        /// </summary>
        public double CurrentError
        {
            get;
            private set;
        }

        private readonly IHyperspaceRandom rng;

        /// <summary>
        /// Gets this particles random number generator
        /// </summary>
        protected IHyperspaceRandom RandomNumberGenerator { get { return rng; } }

        private readonly IObjectiveFunction goodnessFunction;
        private Tribe parent = null;

        /// <summary>
        /// Occurs when the particle has moved
        /// </summary>
        protected event EventHandler<ParticleMovedEventArgs> ParticleMoved = delegate { };

        /// <summary>
        /// Gets a value indicating whether or not this particle is "good"
        /// </summary>
        /// <remarks>
        /// A particle is considered "good" if its best performance at
        /// time t (now) is better than its best performance at time t-1
        /// </remarks>
        public bool IsGood
        {
            get
            {
                if (!this.solutionHistory.Any()) return false;
                else
                {
                    return this.solutionHistory[0];
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether or not this particle is "Excellent"
        /// </summary>
        /// <remarks>
        /// A particle is considered "excellent" if its best performance
        /// at time t (now) is better than its best performance at time t-1 AND its best performance at time t-1 is better
        /// than its best performance at time t-2
        /// </remarks>
        public bool IsExcellent
        {
            get
            {
                if (this.solutionHistory.Count() < 2) return false;
                else
                {
                    return this.solutionHistory[0] && this.solutionHistory[1];
                }
            }
        }

        /// <summary>
        /// Gets the external informers of this particle.  Note that if this particle isn't the best in it's tribe
        /// it won't have any external informers.
        /// </summary>
        /// <remarks>
        /// Note that the collection of ExternalInformers does not contain a self reference.
        /// </remarks>
        public IEnumerable<Particle> ExternalInformers
        {
            get
            {
                //If we aren't in a tribe, we have no external informers so we return an empty set
                //Alternatively, if we're not the shaman of our tribe, we don't have any external informers either
                if (parent == null || parent.Shaman != this) return Enumerable.Empty<Particle>();
                else
                {
                    //Ok, we're in a tribe AND we're the shaman.  We have external links
                    return this.parent.ExternalLinks;
                }
            }
        }

        /// <summary>
        /// Gets or sets the tribe of the particle.  Note that this property can only be set once.  Subsequent attemps to set the parent
        /// tribe results in an InvalidOperationException
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if the caller attempts to set the parent tribe to a different tribe after 
        /// the parent tribe was already set.</exception>
        public Tribe Parent
        {
            get { return parent; }
            set
            {
                if (value == null) throw new ArgumentNullException("value");
                if (parent == null)
                {
                    //Check and make sure the particle really is in the tribe.
                    if (!value.TribeMembers.Contains(this))
                    {
                        throw new InvalidOperationException("The particle is not actually a member of this tribe");
                    }
                    parent = value;
                }
                //We already have a parent.  Check if the value that the caller is trying to set is the parent we already have
                //If it is, we'll let it slide.  If someone is trying to set our parent to a different tribe, then a programming
                //error has occured and it's time to throw.
                else if (parent != value)
                    throw new InvalidOperationException("The parent tribe of a particle cannot be changed once it has been assigned");
            }
        }

        /// <summary>
        /// Gets the internal informers of this particle.  Note that if the particle doesn't belong to a tribe,
        /// it will only have itself as an informer
        /// </summary>
        /// <remarks>
        /// Note that the collection of InternalInformers always contains a self-reference
        /// </remarks>
        public IEnumerable<Particle> InternalInformers
        {
            get
            {
                if (parent == null)
                {
                    return Enumerable.Repeat(this, 1);
                }
                else
                {
                    return this.parent.TribeMembers;
                }
            }
        }

        /// <summary>
        /// Creates a new Particle with the specified objective function and location.
        /// The default HyperspaceRandom random number generator will be used to move the particle
        /// </summary>
        /// <param name="objectiveFunction">The IObjectiveFunction that determines the "goodness" of the particle's position</param>
        /// <param name="initialPosition">The initial location of the particle.  This </param>
        protected Particle(IObjectiveFunction objectiveFunction, EuclidianVector initialPosition)
            : this(objectiveFunction, initialPosition, new HyperspaceRandom()) { }

        /// <summary>
        /// Creates a new Particle with the specified objective function, location and random number generator
        /// </summary>
        /// <param name="objectiveFunction">The IObjectiveFunction that determines the goodness of the particle's position</param>
        /// <param name="initialPosition">The initial position of the particle</param>
        /// <param name="randomNumberGenerator">The random number generator that will be used to move the particle</param>
        protected Particle(
            IObjectiveFunction objectiveFunction,
            EuclidianVector initialPosition,
            IHyperspaceRandom randomNumberGenerator)
        {
            if (objectiveFunction == null) throw new ArgumentNullException("objectiveFunction");
            if (initialPosition == null) throw new ArgumentNullException("initialPosition");
            if (objectiveFunction.Dimensions != initialPosition.Dimensions)
            {
                throw new DimensionMismatchException("Goodness function dimensions don't match position dimensions");
            }
            if (randomNumberGenerator == null) throw new ArgumentNullException("randomNumberGenerator");
            this.solutionHistory = new List<bool>(HistoryLength + 1);
            this.goodnessFunction = objectiveFunction;
            this.rng = randomNumberGenerator;
            this.Position = initialPosition;

            this.BestSolution = new Solution(initialPosition, objectiveFunction.Evaluate(initialPosition));
            this.CurrentError = this.BestSolution.Error;
            this.solutionHistory.AddRange(new bool[] { false, false });
        }

        /// <summary>
        /// Calculates the new position for a particle based on the solution of its best informer
        /// </summary>
        /// <param name="bestInformerSolution"></param>
        /// <returns></returns>
        protected abstract EuclidianVector CalculateNewPosition(Solution bestInformerSolution);

        /// <summary>
        /// Attempts to move the particle.  The particle's new position is calculated based on it's best history and the 
        /// history of it's best informer.
        /// </summary>
        /// <remarks>
        /// If the particle has no informers that are better, it's not going to move
        /// </remarks>
        public void Move()
        {
            /* The original paper doesn't cover some details like "what happens when a particle has no better external imformers.
             * Luckily, I got an answer straight from Maurice Clerc.  His opinion is that a particle that is the best in the
             * swarm (or neighborhood, by extension) should stay put.  If you are going to add random noise to its position, you
             * should normalize the dimensions of the search space first.  I am going to try the first strategy because it 
             * is the easiest to code 
             */
            var Informants = from Informer in this.InternalInformers.Concat(this.ExternalInformers)
                             orderby Informer.BestSolution.Error ascending
                             select Informer;

            Particle BestInformer = Informants.First();
            if (BestInformer != this) //If there's a better informer, we're going to move.  Otherwise we're staying put.
            {
                Solution BestInformerSolution = BestInformer.BestSolution; //Capture the best informer's best solution so it doesn't change out from under us in a multithreaded environment
                EuclidianVector NewPosition = CalculateNewPosition(BestInformerSolution);

                //Now that we've got our new location, check if it's better and do the necessary book keeping if it is
                double NewError = this.goodnessFunction.Evaluate(NewPosition);

                bool improvedBestSolution = NewError < this.BestSolution.Error;

                if (improvedBestSolution)
                    BestSolution = new Solution(NewPosition, NewError);

                MemorizePerformance(improvedBestSolution);

                EuclidianVector oldPosition = Position;
                Position = NewPosition;
                CurrentError = NewError;

                ParticleMoved(this, new ParticleMovedEventArgs(oldPosition, NewPosition));
            }
        }

        /// <summary>
        /// Memorizes the particle's current performance so that it can be used to determine a particle's quality
        /// (IE Good, Exellent, or Neutral)
        /// </summary>
        private void MemorizePerformance(bool improvedBestPerformance)
        {
            this.solutionHistory.Insert(0, improvedBestPerformance);
            //This appears sub-optimal, but in reality the while loop will only get hit once.
            while (this.solutionHistory.Count() > HistoryLength) solutionHistory.RemoveAt(HistoryLength);
        }
    }
}
